using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;
using Phydeaux.Utilities;

namespace BTreeSharp
{
    public class BuiltInByteConverter:IByteConverter
    {
        
        #region IByteConverter Members

        public bool CanConvert(Type t, out int howManyBytes)
        {
            howManyBytes = 0;
            if (t.IsGenericType)
                return false; // cannot deal with generics
            if (t.IsInterface)
                return false; //cannot persist abstract types...
            if (t == typeof(byte))
            {
                howManyBytes = 1;
                return true;
            }
            if (t == typeof(char) || t == typeof(UInt16) || t == typeof(short) )
            {
                howManyBytes = 2;
                return true;
            }
            if (t == typeof(int) || t == typeof(UInt32) || t == typeof(float)  )
            {
                howManyBytes = 4;
                return true;
            }
            if (t == typeof(DateTime) || t == typeof(UInt64) || t == typeof(double) || t == typeof(long))
            {
                howManyBytes = 8;
                return true;
            }
            if (t == typeof(Decimal))
            {
                howManyBytes = 16;
                return true;
            }
            if (t == typeof(string))
            {
                if (!FixedLength(t, out howManyBytes))
                {
                    howManyBytes = sizeof(long); // size required to save the pointer to a variable length string
                }
                return true;
            }
            if (t == typeof(byte[]))
            {

                if (!FixedLength(t, out howManyBytes))
                {
                    howManyBytes = sizeof(long); // size required to save the pointer to a variable length binary
                }
                
                return true;
            }
            if (t is ICollection)
                throw new Exception("POCO objects with collection not implemented yet");
            return false;
        }

        private bool FixedLength(Type t,out int len)
        {
            FixedLenAttribute[] atts = (FixedLenAttribute[])t.GetCustomAttributes(typeof(FixedLenAttribute), true);
            if (atts.Length == 0)
            {
                len = 0;
                return false;
            }
            else
            {
                len = atts[0].Length;
                return true;
            }
        }

        public byte[] Convert(object o)
        {
            
            int dummy;
            if (o == null)
            {
                throw new Exception("Cannot persist null values");
            }
            if (!CanConvert(o.GetType(),out dummy))
            {
                throw new Exception("Cannot persist object of type:"+o.GetType());
            }
            Type t = o.GetType();
            if (t == typeof(byte))
            {
                return new byte[] { (byte)o };
            }
            if (t == typeof(char)  || t == typeof(short))
            {
                return BitConverter.GetBytes(System.Convert.ToInt16(o));
            }
            if (t == typeof(UInt16))
            {
                return BitConverter.GetBytes(System.Convert.ToUInt16(o));
            }
            if (t == typeof(int)  )
            {
                return BitConverter.GetBytes(System.Convert.ToInt32(o));
            }
            if ( t == typeof(UInt32))
            {
                return BitConverter.GetBytes(System.Convert.ToUInt32(o));
            }
            if (t == typeof(float))
            {
                return BitConverter.GetBytes((float)o);
            }
            if (t == typeof(DateTime) )
            {
                return BitConverter.GetBytes(((DateTime)o).ToBinary());
            }
            if (t == typeof(long))
            {
                return BitConverter.GetBytes(System.Convert.ToInt64(o));
            }
            if (t == typeof(UInt64) )
            {
                return BitConverter.GetBytes(System.Convert.ToUInt64(o));
            }
            if ( t == typeof(double) )
            {
                return BitConverter.GetBytes((double)o);
            }
            if (t == typeof(Decimal))
            {
                byte[] b = new byte[16];
                int[] dec = Decimal.GetBits((Decimal)o);
                Array.Copy(BitConverter.GetBytes(dec[0]), 0, b, 0, 4);
                Array.Copy(BitConverter.GetBytes(dec[1]), 0, b, 4, 4);
                Array.Copy(BitConverter.GetBytes(dec[2]), 0, b, 8, 4);
                Array.Copy(BitConverter.GetBytes(dec[3]), 0, b, 12, 4);
                return b;
            }
            if (t == typeof(string))
            {
                if (FixedLength(t, out dummy))
                {
                    byte[] b = new byte[dummy];
                    if( dummy < ((string)o).Length )
                        throw new Exception(string.Format("Cannot persist fixed length string '{0}' because is longer than the fixed length specified:{1}",o,dummy));
                    Array.Copy(UTF32Encoding.UTF32.GetBytes((string)o), 0, b, 0, dummy);
                    return b;
                }
                else
                {
                    return new byte[8];
                }
            }
            if (t == typeof(byte[]))
            {

                if (FixedLength(t, out dummy))
                {
                    byte[] b = new byte[dummy];
                    if (dummy < ((byte[])o).Length)
                        throw new Exception(string.Format("Cannot persist fixed length byte array because is longer than the fixed length specified:{0}", dummy));
                    Array.Copy(UTF32Encoding.UTF32.GetBytes((string)o), 0, b, 0, dummy);

                }
                else
                {
                    return new byte[8];
                }

            }
            throw new Exception("Cannot persist type:"+ t.ToString());

        }
        public object Rehydrate(byte[] b,Type expected)
        {
            if (expected == typeof(byte))
            {
                return b[0];
            }
            if (expected == typeof(char))
            {
                return BitConverter.ToChar(b,0);
            }
            if(  expected == typeof(UInt16) )
            {
                return BitConverter.ToUInt16(b,0);
            }
            if( expected == typeof(short ) )
            {
                return BitConverter.ToInt16(b, 0);
            }
            if (expected == typeof(int) ) 
            {
                return BitConverter.ToInt32(b,0);
            }
            if( expected == typeof(UInt32) )
            {
                return BitConverter.ToUInt32(b,0);
            }
            if( expected == typeof(float) )
            {
                return BitConverter.ToSingle(b,0);
            }
            if (expected == typeof(DateTime) ) 
            {
                long l = BitConverter.ToInt64(b,0);
                return DateTime.FromBinary(l);
            }
            if( expected == typeof(UInt64) )
            {
                return BitConverter.ToUInt64(b,0);
            }
            if( expected == typeof(double) )
            {
                return BitConverter.ToDouble(b,0);
            }
            if( expected == typeof(long) )
            {
                return BitConverter.ToInt64(b,0);
            }
            if (expected == typeof(Decimal))
            {
                int[] bits = new int[4];
                bits[0] = BitConverter.ToInt32(b,0);
                bits[1] = BitConverter.ToInt32(b,4);
                bits[2] = BitConverter.ToInt32(b,8);
                bits[3] = BitConverter.ToInt32(b,12);
                return new decimal(bits);
            }
            if (expected == typeof(string))
            {
                throw new NotImplementedException();
            }
            if (expected == typeof(byte[]))
            {
                throw new NotImplementedException();
                
            }
            throw new Exception("Cannot Rehydrate type:" + expected.ToString());

        }
        #endregion
    }
}
